﻿using System;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using Coding4Fun.BoxingBots.Common;
using NAudio.Wave;

namespace Coding4Fun.BoxingBots.Scoreboard
{
	/// <summary>
	/// Interaction logic for AdminWindow.xaml
	/// </summary>
	public partial class AdminWindow : Window
	{
		private UserConsole _console1 = new UserConsole();
		private UserConsole _console2 = new UserConsole();

		private Robot _robot1 = new Robot();
		private Robot _robot2 = new Robot();

		private readonly Stopwatch _gameWatch = new Stopwatch();

		private readonly SolidColorBrush _red = new SolidColorBrush(Color.FromArgb(255, 220, 20, 60));
		private readonly SolidColorBrush _green = new SolidColorBrush(Color.FromArgb(255, 0, 168, 119));

		private GameState _gameState;

		private WaveOut _waveOut;
		private BufferedWaveProvider _provider;
		
		public bool ConnectedOverride { get; set; }
		private bool _runGameLoop = true;

		public AdminWindow()
		{
			InitializeComponent();
		}

		private void Window_Loaded(object sender, RoutedEventArgs e)
		{
			this.DataContext = this;

			_robot1.MovementOverrideChanged += Robot1MovementOverrideChanged;
			_robot2.MovementOverrideChanged += Robot2MovementOverrideChanged;

			InitializeKinectAudio();

			Thread gameThread = new Thread(GameThread);
			gameThread.IsBackground = true;
			gameThread.Start();
		}

		private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
		{
			_runGameLoop = false;

			Thread.Sleep(100);

			AudioPlayer.Shutdown();

			if(_waveOut != null)
			{
				_waveOut.Stop();
				_waveOut.Dispose();
			}
			
			_robot1.Dispose();
			_robot2.Dispose();

			_console1.Dispose();
			_console2.Dispose();

			_robot1 = null;
			_robot2 = null;

			_console1 = null;
			_console2 = null;

			_waveOut = null;
		}

		private void InitializeKinectAudio()
		{
			if (Properties.Settings.Default.MuteKinectAudio)
			{
				MuteKinectAudio.IsEnabled = false;
			}
			else
			{
				_waveOut = new WaveOut();
				WaveFormat format = new WaveFormat(16000, 16, 1);
				_provider = new BufferedWaveProvider(format);
				_waveOut.Init(_provider);
				_waveOut.Play();
			}
		}

		private void ConnectUserConsole1_Click(object sender, RoutedEventArgs e)
		{
			_console1.Robot = _robot1;

			_console1.ColorFrameReady += _console1_ColorFrameReady;
			_console1.SkeletonFrameReady += _console1_SkeletonFrameReady;
			_console1.AudioFrameReady += _console1_AudioFrameReady;
			_console1.Connect(Settings.NetworkSettings[Player.One].UserConsoleIpAddress, 
								Settings.NetworkSettings[Player.One].ColorPort,
								Settings.NetworkSettings[Player.One].SkeletonPort,
								Settings.NetworkSettings[Player.One].AudioPort,
								Settings.NetworkSettings[Player.One].DataPort);
		}

		void _console1_ColorFrameReady(object sender, Kinect.KinectService.Common.ColorFrameReadyEventArgs e)
		{
			this.Video1.Source = e.ColorFrame.BitmapImage;
		}

		void _console1_SkeletonFrameReady(object sender, Kinect.KinectService.Common.SkeletonFrameReadyEventArgs e)
		{
			// TODO: draw this or something
			Skeleton1.Text = e.SkeletonFrame.Skeletons[0].TrackingState.ToString();
		}

		void _console1_AudioFrameReady(object sender, Kinect.KinectService.Common.AudioFrameReadyEventArgs e)
		{
			if(!MuteKinectAudio.IsChecked.Value)
				_provider.AddSamples(e.AudioFrame.AudioData, 0, e.AudioFrame.AudioData.Length);
		}

		void Robot1MovementOverrideChanged(object sender, MovementOverrideChangedEventArgs e)
		{
			if(e.MovementOverride)
				SetGameState(GameState.Paused);
		}

		private void ConnectUserConsole2_Click(object sender, RoutedEventArgs e)
		{
			_console2.Robot = _robot2;

			_console2.ColorFrameReady += _console2_ColorFrameReady;
			_console2.SkeletonFrameReady += _console2_SkeletonFrameReady;
			_console2.AudioFrameReady += _console2_AudioFrameReady;
			_console2.Connect(Settings.NetworkSettings[Player.Two].UserConsoleIpAddress, 
								Settings.NetworkSettings[Player.Two].ColorPort,
								Settings.NetworkSettings[Player.Two].SkeletonPort,
								Settings.NetworkSettings[Player.Two].AudioPort,
								Settings.NetworkSettings[Player.Two].DataPort);
		}

		void _console2_ColorFrameReady(object sender, Kinect.KinectService.Common.ColorFrameReadyEventArgs e)
		{
			Video2.Source = e.ColorFrame.BitmapImage;
		}

		void _console2_SkeletonFrameReady(object sender, Kinect.KinectService.Common.SkeletonFrameReadyEventArgs e)
		{
			// TODO: draw this or something
			Skeleton1.Text = e.SkeletonFrame.Skeletons[0].TrackingState.ToString();
		}

		void _console2_AudioFrameReady(object sender, Kinect.KinectService.Common.AudioFrameReadyEventArgs e)
		{
			if(MuteKinectAudio.IsChecked.HasValue && !MuteKinectAudio.IsChecked.Value)
				_provider.AddSamples(e.AudioFrame.AudioData, 0, e.AudioFrame.AudioData.Length);
		}

		void Robot2MovementOverrideChanged(object sender, MovementOverrideChangedEventArgs e)
		{
			if(e.MovementOverride)
				SetGameState(GameState.Paused);
		}

		private void SetGameState(GameState gs)
		{
			if(gs == GameState.Started && _gameState == GameState.GameOver)
				_gameWatch.Reset();

			if(gs == GameState.Started)
			{
				Dispatcher.BeginInvoke(new Action(() => StartGame.IsEnabled = false));
				_gameWatch.Start();
			}

			if(gs == GameState.Paused)
				_gameWatch.Stop();

			if(gs == GameState.GameOver)
			{
				Dispatcher.BeginInvoke(new Action(() => StartGame.IsEnabled = true));
				_gameWatch.Reset();
			}

			_gameState = gs;
		}


		private void ConnectRobot1_Click(object sender, RoutedEventArgs e)
		{
			_robot1.Connect(Settings.NetworkSettings[Player.One].RobotIpAddress, Settings.NetworkSettings[Player.One].RobotDataPort);
		}

		private void ConnectRobot2_Click(object sender, RoutedEventArgs e)
		{
			_robot2.Connect(Settings.NetworkSettings[Player.Two].RobotIpAddress, Settings.NetworkSettings[Player.Two].RobotDataPort);
		}

		private void ConnectAll_Click(object sender, RoutedEventArgs e)
		{
			ConnectUserConsole1_Click(null, null);
			ConnectUserConsole2_Click(null, null);
			ConnectRobot1_Click(null, null);
			ConnectRobot2_Click(null, null);
		}

		private void StartGame_Click(object sender, RoutedEventArgs e)
		{
			// some kind of countdown first...3...2..1...Box!!

			_robot1.Reset();
			_robot2.Reset();

			SetGameState(GameState.Started);
		}

		private void PauseGame_Click(object sender, RoutedEventArgs e)
		{
			switch(_gameState)
			{
				case GameState.Started:
					SetGameState(GameState.Paused);
					break;
				case GameState.Paused:
					SetGameState(GameState.Started);
					break;
			}
		}

		private void EndGame_Click(object sender, RoutedEventArgs e)
		{
			SetGameState(GameState.GameOver);
			StartGame.IsEnabled = true;
		}

		private void GameThread()
		{
			while (_runGameLoop)
			{
				if((!_console1.IsConnected || !_console2.IsConnected || !_robot1.IsConnected || !_robot2.IsConnected) && !ConnectedOverride)
					SetGameState(GameState.Paused);

				double ellapsedMilliseconds = ((Settings.RoundLength * 1000) - _gameWatch.ElapsedMilliseconds);
				if(ellapsedMilliseconds <= 0 || _robot1.Health <= 0 || _robot2.Health <= 0)
				{
					ellapsedMilliseconds = 0;
					SetGameState(GameState.GameOver);
				}

				GameStatePacket gsp = new GameStatePacket
				{
					Robot1Health = _robot1.Health,
					Robot2Health = _robot2.Health,
					Robot1SensorData = _robot1.SensorData,
					Robot2SensorData = _robot2.SensorData,
					LockSkeleton1 = _robot1.LockSkeleton,
					LockSkeleton2 = _robot2.LockSkeleton,
					GameState = _gameState
				};

				_console1.Send(gsp);
				_console2.Send(gsp);

				string time = (ellapsedMilliseconds/1000.0).ToString("0.0");
				UpdateUI(time);

				Thread.Sleep(33);
			}
		}

		private void UpdateUI(string timer)
		{
			if (!_runGameLoop)
				return;

			Dispatcher.BeginInvoke(new Action(() => 
			{
				SetStatus(Console1Connected, _console1 != null && _console1.IsConnected);
				SetStatus(Console2Connected, _console1 != null && _console2.IsConnected);

				if (_robot1 != null)
				{
					SetStatus(Robot1Connected, _robot1.IsConnected);
					SetStatus(OverrideDisplay1, _robot1.MovementOverride);
					Robot1Health.Text = _robot1.Health.ToString();
					Robot1HealthSlider.Value = _robot1.Health;
				}

				if (_robot2 != null)
				{
					SetStatus(Robot2Connected, _robot2.IsConnected);
					SetStatus(OverrideDisplay2, _robot2.MovementOverride);
					Robot2Health.Text = _robot2.Health.ToString();
					Robot2HealthSlider.Value = _robot2.Health;
				}

				GameStateDisplay.Text = _gameState.ToString();

				Timer.Text = timer;
			}));
		}

		private void SetStatus(Run sender, bool isConnected)
		{
			sender.Text = isConnected.ToString();
			sender.Foreground = (isConnected) ? _green : _red;
		}

		private void Robot1HealthSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
		{
			_robot1.Health = (int)e.NewValue;
		}

		private void Robot2HealthSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
		{
			_robot2.Health = (int)e.NewValue;
		}

		private void ResetGame_Click(object sender, RoutedEventArgs e)
		{
			SetGameState(GameState.GameOver);
			_robot1.Reset();
			_robot2.Reset();
			_robot1.Send(new MovementDescriptorPacket { Health = _robot1.Health });
			_robot2.Send(new MovementDescriptorPacket { Health = _robot2.Health });
			_console1.Send(new GameStatePacket { GameState = GameState.Reset });
			_console2.Send(new GameStatePacket { GameState = GameState.Reset });
		}

		private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
		{
			if(e.IsRepeat)
				return;

			switch(e.Key)
			{
				case Key.F1:
					StartGame_Click(this, null);
					break;
				case Key.F2:
					PauseGame_Click(this, null);
					break;
				case Key.F3:
					EndGame_Click(this, null);
					break;
				case Key.F4:
					ResetGame_Click(this, null);
					break;
			}
		}
	}
}
